<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  <meta http-equiv="Content-Style-Type" content="text/css" />
  <meta name="generator" content="pandoc" />
  <title>Linux内核源码解读</title>
  <style type="text/css">code{white-space: pre;}</style>
  <style type="text/css">
div.sourceCode { overflow-x: auto; }
table.sourceCode, tr.sourceCode, td.lineNumbers, td.sourceCode {
  margin: 0; padding: 0; vertical-align: baseline; border: none; }
table.sourceCode { width: 100%; line-height: 100%; }
td.lineNumbers { text-align: right; padding-right: 4px; padding-left: 4px; color: #aaaaaa; border-right: 1px solid #aaaaaa; }
td.sourceCode { padding-left: 5px; }
code > span.kw { color: #007020; font-weight: bold; } /* Keyword */
code > span.dt { color: #902000; } /* DataType */
code > span.dv { color: #40a070; } /* DecVal */
code > span.bn { color: #40a070; } /* BaseN */
code > span.fl { color: #40a070; } /* Float */
code > span.ch { color: #4070a0; } /* Char */
code > span.st { color: #4070a0; } /* String */
code > span.co { color: #60a0b0; font-style: italic; } /* Comment */
code > span.ot { color: #007020; } /* Other */
code > span.al { color: #ff0000; font-weight: bold; } /* Alert */
code > span.fu { color: #06287e; } /* Function */
code > span.er { color: #ff0000; font-weight: bold; } /* Error */
code > span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warning */
code > span.cn { color: #880000; } /* Constant */
code > span.sc { color: #4070a0; } /* SpecialChar */
code > span.vs { color: #4070a0; } /* VerbatimString */
code > span.ss { color: #bb6688; } /* SpecialString */
code > span.im { } /* Import */
code > span.va { color: #19177c; } /* Variable */
code > span.cf { color: #007020; font-weight: bold; } /* ControlFlow */
code > span.op { color: #666666; } /* Operator */
code > span.bu { } /* BuiltIn */
code > span.ex { } /* Extension */
code > span.pp { color: #bc7a00; } /* Preprocessor */
code > span.at { color: #7d9029; } /* Attribute */
code > span.do { color: #ba2121; font-style: italic; } /* Documentation */
code > span.an { color: #60a0b0; font-weight: bold; font-style: italic; } /* Annotation */
code > span.cv { color: #60a0b0; font-weight: bold; font-style: italic; } /* CommentVar */
code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Information */
  </style>
  <link rel="stylesheet" href="doc/linux-style.css" type="text/css" />
</head>
<body>
<div id="header">
<h1 class="title">Linux内核源码解读</h1>
</div>
<div id="TOC">
<ul>
<li><a href="#linux内核源码解读">Linux内核源码解读</a><ul>
<li><a href="#通过qemu运行一个最小系统">通过qemu运行一个最小系统</a></li>
<li><a href="#linux网络设备驱动开发思路指南">Linux网络设备驱动开发思路指南</a></li>
<li><a href="#linux中断">Linux中断</a></li>
<li><a href="#编写一个最简单的字符设备驱动">编写一个最简单的字符设备驱动</a></li>
<li><a href="#内核空间和用户空间的概念以及内核空间和用户空间的内存拷贝">内核空间和用户空间的概念以及内核空间和用户空间的内存拷贝</a></li>
<li><a href="#x86段页式内存管理和页表映射机制">x86段页式内存管理和页表映射机制</a></li>
<li><a href="#linux内核同步场景以及解决之道">linux内核同步场景以及解决之道</a></li>
<li><a href="#char设备驱动到platform驱动的华丽转身">char设备驱动到platform驱动的华丽转身</a></li>
<li><a href="#页框和伙伴算法以及slab机制">页框和伙伴算法以及slab机制</a></li>
</ul></li>
</ul>
</div>
<h1 id="linux内核源码解读">Linux内核源码解读</h1>
<h2 id="通过qemu运行一个最小系统">通过qemu运行一个最小系统</h2>
<h3 id="下载linux内核源码和busybox源码">下载linux内核源码和busybox源码</h3>
<p>我下载的版本是Linux内核4.9.229版本 <a href="https://mirrors.edge.kernel.org/pub/linux/kernel/">linux源码下载地址</a> 我下载的busybos源码版本是1.36.1版本 <a href="https://busybox.net/downloads/">busybox源码下载地址</a></p>
<pre><code># 下载最新稳定版（如 1.36.1）
wget https://busybox.net/downloads/busybox-1.36.1.tar.bz2
tar -xf busybox-1.36.1.tar.bz2
cd busybox-1.36.1
make menuconfig #可以根据需求做一些特定的修改
make 
make install</code></pre>
<p>make install 后我们的busybox的内容是不完整的，可以去修改busybox的配置。 参考b站 <code>简说linux</code> <a href="https://space.bilibili.com/646178510/lists?sid=375089&amp;spm_id_from=333.788.0.0">b站视频链接</a> 将源码编译后用qemu运行。</p>
<h3 id="通过qemu启动内核">通过qemu启动内核</h3>
<p>qemu启动脚本：<code>vim start.sh</code></p>
<pre><code>qemu-system-x86_64 -kernel ./linux-4.9.229/arch/x86_64/boot/bzImage  -initrd ./busybox-1.36.1/rootfs.img.gz  -app
end &quot;root=/dev/ram init=/linuxrc&quot; -serial file:output.tx</code></pre>
<h3 id="内核源码各个目录的解读">内核源码各个目录的解读</h3>
<h4 id="核心功能目录">核心功能目录</h4>
<ol style="list-style-type: decimal">
<li><strong><code>arch</code></strong><br />
</li>
</ol>
<ul>
<li><strong>架构相关代码</strong>：每个子目录对应一种CPU架构（如<code>x86</code>、<code>arm</code>、<code>arm64</code>）。</li>
<li><strong>关键内容</strong>：处理器调度、内存管理、中断处理等硬件相关实现。</li>
</ul>
<ol start="2" style="list-style-type: decimal">
<li><strong><code>kernel</code></strong><br />
</li>
</ol>
<ul>
<li><strong>核心子系统</strong>：进程调度（<code>sched/</code>）、信号处理（<code>signal.c</code>）、系统调用（<code>sys.c</code>）等。</li>
<li><strong>基础机制</strong>：定时器、RCU锁、模块加载等。</li>
</ul>
<ol start="3" style="list-style-type: decimal">
<li><strong><code>mm</code></strong><br />
</li>
</ol>
<ul>
<li><strong>内存管理</strong>：物理内存分配（<code>page_alloc.c</code>）、虚拟内存（<code>vmalloc.c</code>）、页表管理（<code>pgtable.c</code>）等。</li>
</ul>
<ol start="4" style="list-style-type: decimal">
<li><strong><code>init</code></strong><br />
</li>
</ol>
<ul>
<li><strong>内核启动流程</strong>：<code>main.c</code>包含内核初始化入口（<code>start_kernel</code>）。</li>
<li><strong>早期用户空间初始化</strong>（如<code>initramfs</code>）。</li>
</ul>
<h4 id="驱动与设备">驱动与设备</h4>
<ol style="list-style-type: decimal">
<li><strong><code>drivers</code></strong><br />
</li>
</ol>
<ul>
<li><strong>设备驱动</strong>：按设备类型分类，如<code>char/</code>（字符设备）、<code>pci/</code>、<code>usb/</code>、<code>net/</code>等。</li>
<li><strong>总线与平台驱动</strong>：<code>i2c/</code>、<code>spi/</code>、<code>platform/</code>。</li>
</ul>
<ol start="2" style="list-style-type: decimal">
<li><strong><code>block</code></strong><br />
</li>
</ol>
<ul>
<li><strong>块设备层</strong>：I/O调度算法（<code>deadline-iosched.c</code>）、通用块设备驱动。</li>
</ul>
<h4 id="文件系统与网络">文件系统与网络</h4>
<ol style="list-style-type: decimal">
<li><strong><code>fs</code></strong><br />
</li>
</ol>
<ul>
<li><strong>文件系统实现</strong>：<code>ext4/</code>、<code>btrfs/</code>、<code>proc/</code>、<code>sysfs/</code>等。</li>
<li><strong>虚拟文件系统（VFS）</strong>：<code>vfs/</code>目录下的通用文件操作接口。</li>
</ul>
<ol start="2" style="list-style-type: decimal">
<li><strong><code>net</code></strong></li>
</ol>
<ul>
<li><strong>网络协议栈</strong>：IPv4/IPv6（<code>ipv4/</code>、<code>ipv6/</code>）、TCP/UDP、Socket层。</li>
<li><strong>协议实现</strong>：<code>ethernet/</code>、<code>wireless/</code>、<code>mac80211/</code>等。</li>
</ul>
<h4 id="工具与辅助模块">工具与辅助模块</h4>
<ol style="list-style-type: decimal">
<li><strong><code>lib</code></strong><br />
</li>
</ol>
<ul>
<li><strong>通用库函数</strong>：字符串处理、CRC校验、链表实现等。</li>
</ul>
<ol start="2" style="list-style-type: decimal">
<li><strong><code>scripts</code></strong><br />
</li>
</ol>
<ul>
<li><strong>构建与配置脚本</strong>：内核编译工具链（如<code>Kconfig</code>解析、<code>Makefile</code>生成）。</li>
</ul>
<ol start="3" style="list-style-type: decimal">
<li><strong><code>tools</code></strong><br />
</li>
</ol>
<ul>
<li><strong>用户态工具</strong>：性能分析工具（如<code>perf/</code>）、调试工具。</li>
</ul>
<h4 id="安全与虚拟化">安全与虚拟化</h4>
<ol style="list-style-type: decimal">
<li><strong><code>security</code></strong><br />
</li>
</ol>
<ul>
<li><strong>安全模块</strong>：SELinux、AppArmor、SMACK等实现。</li>
</ul>
<ol start="2" style="list-style-type: decimal">
<li><strong><code>virt</code></strong><br />
</li>
</ol>
<ul>
<li><strong>虚拟化支持</strong>：KVM（Kernel-based Virtual Machine）相关代码。</li>
</ul>
<h4 id="文档与示例">文档与示例</h4>
<ol style="list-style-type: decimal">
<li><strong><code>Documentation</code></strong><br />
</li>
</ol>
<ul>
<li><strong>内核文档</strong>：配置指南、API说明、子系统设计文档。</li>
</ul>
<ol start="2" style="list-style-type: decimal">
<li><strong><code>samples</code></strong><br />
</li>
</ol>
<ul>
<li><strong>示例代码</strong>：内核模块、BPF程序等示例。</li>
</ul>
<h4 id="其他重要目录">其他重要目录</h4>
<ul>
<li><strong><code>include</code></strong>：内核头文件（分为架构相关头文件<code>asm/</code>和通用头文件<code>linux/</code>）。</li>
<li><strong><code>ipc</code></strong>：进程间通信机制（如共享内存、信号量、消息队列）。</li>
<li><strong><code>crypto</code></strong>：加密算法实现（如AES、SHA系列）。</li>
<li><strong><code>sound</code></strong>：音频子系统与驱动。</li>
<li><strong><code>usr</code></strong>：早期用户空间支持（如<code>initramfs</code>构建）。</li>
</ul>
<p>可以结合<code>Documentation/</code>和在线内核文档（如 <a href="https://www.kernel.org/doc/">kernel.org/doc</a>）获得详细内容。</p>
<h2 id="linux网络设备驱动开发思路指南">Linux网络设备驱动开发思路指南</h2>
<h3 id="loopback设备">loopback设备：</h3>
<ul>
<li>mtu :1460 Byte</li>
<li>数据链路层头部：14 Byte</li>
<li>以太网物理地址: 6 Byte</li>
</ul>
<h3 id="loopback设备的功能">loopback设备的功能</h3>
<ul>
<li>通过ping测试数据链路层、IP层的功能是否有效(ping是ICMP报文替代了TCP报文)</li>
<li>实现同一台设备上不同进程之间的通信;对于不同的设备上，可以通过物理网卡就可以实现通信，IP地址识别不同的设备，通过TCP/UDP的端口号识别进程。有了loopback，就可以实现不同进程之间的通信。</li>
</ul>
<h3 id="核心开发步骤">核心开发步骤</h3>
<h4 id="注册网络设备">1. 注册网络设备</h4>
<ul>
<li><strong>分配网络设备号</strong>：使用<code>alloc_netdev</code>或<code>register_netdev</code>分配设备号和初始化网络设备结构体。</li>
<li><strong>初始化设备结构体</strong>：设置设备名、设备类型、硬件地址等基本信息。</li>
</ul>
<h4 id="实现网络设备操作">2. 实现网络设备操作</h4>
<ul>
<li><strong>初始化函数</strong>：<code>netdev_init</code>用于初始化网络设备结构体。</li>
<li><strong>打开和关闭设备</strong>：实现<code>open</code>和<code>stop</code>函数，处理设备的打开和关闭。</li>
<li><strong>数据传输</strong>：实现<code>hard_start_xmit</code>函数，处理数据包的发送。</li>
<li><strong>接收数据</strong>：实现<code>set_rx_mode</code>和<code>netif_rx</code>函数，处理数据包的接收。</li>
</ul>
<h4 id="注册网络协议栈">3. 注册网络协议栈</h4>
<ul>
<li><strong>注册协议</strong>：使用<code>dev_add</code>将网络设备添加到协议栈中。</li>
<li><strong>设置网络协议</strong>：配置设备支持的协议类型，如IPv4、IPv6等。</li>
</ul>
<h4 id="处理网络事件">4. 处理网络事件</h4>
<ul>
<li><strong>设备状态变化</strong>：实现<code>change_mtu</code>、<code>do_ioctl</code>等函数，处理设备状态变化和用户空间请求。</li>
<li><strong>错误处理</strong>：实现<code>tx_timeout</code>函数，处理发送超时等错误情况。</li>
</ul>
<h4 id="卸载和清理">5. 卸载和清理</h4>
<ul>
<li><strong>卸载驱动</strong>：实现<code>unregister_netdev</code>和<code>free_netdev</code>函数，用于卸载驱动和释放资源。</li>
<li><strong>清理工作</strong>：确保在驱动卸载时，所有资源都被正确释放，没有内存泄漏。</li>
</ul>
<h4 id="用户空间接口">6. 用户空间接口</h4>
<ul>
<li><strong>设备节点</strong>：通过<code>udev</code>规则自动创建设备节点，或手动使用<code>mknod</code>命令创建。</li>
<li><strong>网络配置</strong>：使用<code>ifconfig</code>或<code>ip</code>命令配置网络设备，如IP地址、子网掩码等。</li>
</ul>
<h4 id="测试和调试">7. 测试和调试</h4>
<ul>
<li><strong>单元测试</strong>：编写测试用例，验证驱动的各个功能模块。</li>
<li><strong>集成测试</strong>：在实际网络环境中测试驱动的稳定性和性能。</li>
<li><strong>调试工具</strong>：使用<code>dmesg</code>、<code>tcpdump</code>等工具进行调试和分析。</li>
</ul>
<h2 id="linux中断">Linux中断</h2>
<ul>
<li>硬件中断:由外部设备（如键盘、网卡、磁盘控制器）通过物理信号发送到CPU</li>
<li>软件中断: 由程序主动发起，通常用于系统调用或异常处理。</li>
<li>Linux中的中断处理机制：上半部用于快速、不可中断，处理紧急任务，如读取硬件状态，下半部处理耗时操作（如数据处理），避免阻塞其他中断</li>
</ul>
<h2 id="编写一个最简单的字符设备驱动">编写一个最简单的字符设备驱动</h2>
<h3 id="核心开发步骤-1">核心开发步骤</h3>
<h4 id="设备号管理">1. 设备号管理</h4>
<ul>
<li><strong>动态分配</strong>：优先使用<code>alloc_chrdev_region</code></li>
<li><strong>静态注册</strong>：已知主设备号时使用<code>register_chrdev_region</code></li>
<li><strong>设备号组成</strong>：主设备号(12bit) + 次设备号(20bit)</li>
</ul>
<h4 id="设备对象初始化">2. 设备对象初始化</h4>
<ul>
<li><strong>cdev结构体</strong>：表征字符设备的核心数据结构</li>
<li><strong>内存分配</strong>：动态(<code>cdev_alloc</code>) vs 静态(<code>struct cdev</code>)</li>
</ul>
<h4 id="操作接口绑定">3. 操作接口绑定</h4>
<ul>
<li><strong>file_operations</strong>：定义设备功能接口的蓝图</li>
<li><strong>关键操作</strong>：至少实现<code>open</code>和<code>release</code></li>
<li><strong>功能扩展</strong>：按需实现<code>read/write/ioctl</code>等</li>
</ul>
<h4 id="系统注册流程">4. 系统注册流程</h4>
<ol style="list-style-type: decimal">
<li>初始化设备对象(<code>cdev_init</code>)</li>
<li>设置所属模块(<code>cdev.owner = THIS_MODULE</code>)</li>
<li>添加至系统(<code>cdev_add</code>)</li>
</ol>
<h4 id="用户空间接口-1">5. 用户空间接口</h4>
<ul>
<li><strong>手动创建</strong>：<code>mknod</code>命令创建设备节点</li>
<li><p><strong>自动创建</strong>：通过<code>class_create</code>+<code>device_create</code></p></li>
<li><p><strong>编写驱动</strong></p></li>
</ul>
<p>一般需要注册设备号，分配cdev内存(cdev是字符设备的结构体)，如果需要自动创建设备节点，还需要创建calss和device，如果手动创建设备节点则用mknod，然后实现file_operations一些操作绑定，初始化cdev对象。</p>
<p>手动创建设备节点则需要：<code>mknod /dev/hello c 232 0</code> 主要需要体现设备节点名称，以便于应用程序打开调用，c为字符设备描述，232为主设备号，0为次设备号。</p>
<ul>
<li><strong>应用程序驱动</strong></li>
</ul>
<p>一般就是在应用层调用 file 的open、read、write等等操作。</p>
<p>应用层调用wite函数-&gt;c库write函数-&gt;系统调用，内核的wite会判断你的file_poerations 的write函数是否定义，定义了则调用你写的哪个驱动wirte函数。</p>
<ul>
<li><strong>字符设备驱动代码添加到内核源码树</strong></li>
</ul>
<p>字符设备驱动一般放在drivers/char 文件夹下面，把你编写的字符设备驱动.c拷贝到当前目录，然后去修改Kconig 文件，修改很简单，可以复制粘贴Kconfig里面已有的内容： <code>sh    config_HELLO       tristate &quot;hello_device&quot;   #tristate三个选项 y m n bool是两个选项: y,n       default y       help           hello device</code></p>
<p>然后通过make menuconfig就可以看到hello_device在内核源码树里面了。</p>
<p>编译之前，需要在makefile文件里面假如<code>obj-$(config_HELLO) +=hello_Dev.o</code> 自动读取config_HELLO 是为 y、 n、m等参数。</p>
<p>说白了驱动文件分为动态加载和静态加载，静态加载就是驱动文件写入内核中，不需要手动insmod 加载驱动.ko文件。</p>
<h2 id="内核空间和用户空间的概念以及内核空间和用户空间的内存拷贝">内核空间和用户空间的概念以及内核空间和用户空间的内存拷贝</h2>
<ul>
<li><strong>以32位系统为例</strong></li>
</ul>
<p><code>2^32 bit = 2^22 Byte = 2^12 MB = 2^2 GB = 4GB</code></p>
<p>系统的用户空间从地址的 0x00000000 - 0xBFFFFFFF(3GB)</p>
<p>系统的内核空间从地址的 0xc0000000 - 0xFFFFFFFF(1GB)</p>
<p>当然这大小可以选择配置，通过配置<code>PAGE_OFFSET</code></p>
<p>x86，用户运行在Ring3模式，内核运行在Ring0模式 arm，用户运行在usr模式，内核运行在svc模式</p>
<ul>
<li><strong>用户空间代码怎么调入到内核中</strong></li>
</ul>
<p>当用户调用系统调用的时候，会触发一个中断，从而进入到内核。这些其实都在用户空间的进程中进行，而且此时会被硬件中断打断。</p>
<ul>
<li><strong>内核空间和用户空间的拷贝</strong></li>
</ul>
<p>write驱动函数使用copy_from_user函数，需要在内核驱动函数里面的多增加一个参数是用户空间buf缓冲区的地址。</p>
<p>read驱动函数使用copy_to_user函数</p>
<p>这两个函数看起来确实挺简单，但是底层逻辑来讲，是先通过线性地址读取物理地址的内容，然后拷贝在通过映射到线性地址给用户，如果内存中不存在，需要去磁盘寻找，效率会大打折扣。</p>
<h2 id="x86段页式内存管理和页表映射机制">x86段页式内存管理和页表映射机制</h2>
<p>逻辑地址 ---(段管理) --&gt; 线性地址 ---(页管理)--&gt;物理地址</p>
<p>在linux内核里面的段选择Segment Selcetor设置为了0,也就是其实逻辑地址就是线性地址。</p>
<p>关于二级页表的映射</p>
<p>线性地址是32位，高10位为页表目录，中10位位页表项，低12位为偏移量，先从页表目录索引，然后再索引页表项，然后得到物理块编号 + offset 就可以得到 物理地址。</p>
<p>每个进程的页表目录不同，就不会造成不同进程之间有访问其他数据的情况了。</p>
<h2 id="linux内核同步场景以及解决之道">linux内核同步场景以及解决之道</h2>
<ul>
<li><strong>信号量</strong></li>
</ul>
<p>对某个设备的访问，只允许一个进程进行访问，需要对这个资源进行互斥访问，访问资源的代码称为临界区。因此需要用到信号量semaphore。通过down(加锁) 和 up(释放锁)。</p>
<p>在其他进程想要访问临界区代码的时候，如果没有获取到锁则会休眠</p>
<ul>
<li><strong>semaphore的内核源码实现</strong></li>
</ul>
<p>主要看down 和 up 的实现，list_head 用于连接所有等待信号量的进程 semaphore_waiter表示一个正在等待信号量的进程。</p>
<div class="sourceCode"><pre class="sourceCode c"><code class="sourceCode c"><span class="co">// include/linux/semaphore.h</span>
<span class="kw">struct</span> semaphore {
    raw_spinlock_t      lock;       <span class="co">// 保护信号量的自旋锁</span>
    <span class="dt">unsigned</span> <span class="dt">int</span>        count;      <span class="co">// 资源计数器</span>
    <span class="kw">struct</span> list_head    wait_list;  <span class="co">// 等待队列</span>
};

<span class="co">// 等待队列节点</span>
<span class="kw">struct</span> semaphore_waiter {
    <span class="kw">struct</span> list_head list;
    <span class="kw">struct</span> task_struct *task;       <span class="co">// 等待的进程</span>
    bool up;                       <span class="co">// 是否已被唤醒</span>
};

<span class="co">// kernel/locking/semaphore.c</span>
<span class="dt">void</span> sema_init(<span class="kw">struct</span> semaphore *sem, <span class="dt">int</span> val)
{
    <span class="dt">static</span> <span class="kw">struct</span> lock_class_key __key;
    *sem = (<span class="kw">struct</span> semaphore) __SEMAPHORE_INITIALIZER(*sem, val);
    lockdep_init_map(&amp;sem-&gt;lock.dep_map, <span class="st">&quot;semaphore-&gt;lock&quot;</span>, &amp;__key, <span class="dv">0</span>);
}

<span class="dt">void</span> down(<span class="kw">struct</span> semaphore *sem)
{
    <span class="dt">unsigned</span> <span class="dt">long</span> flags;
    
    <span class="co">// 尝试快速获取</span>
    <span class="cf">if</span> (likely(sem-&gt;count &gt; <span class="dv">0</span>)) {
        sem-&gt;count--;
        <span class="cf">return</span>;
    }
    
    raw_spin_lock_irqsave(&amp;sem-&gt;lock, flags);
    <span class="cf">if</span> (likely(sem-&gt;count &gt; <span class="dv">0</span>)) {
        sem-&gt;count--;
        raw_spin_unlock_irqrestore(&amp;sem-&gt;lock, flags);
        <span class="cf">return</span>;
    }
    
    <span class="co">// 资源不足，进入等待</span>
    __down(sem);
    raw_spin_unlock_irqrestore(&amp;sem-&gt;lock, flags);
}

<span class="dt">static</span> noinline <span class="dt">void</span> __sched __down(<span class="kw">struct</span> semaphore *sem)
{
    __down_common(sem, TASK_UNINTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT);
}

<span class="dt">static</span> <span class="kw">inline</span> <span class="dt">int</span> __sched __down_common(<span class="kw">struct</span> semaphore *sem, <span class="dt">long</span> state,
                                <span class="dt">long</span> timeout)
{
    <span class="kw">struct</span> semaphore_waiter waiter;
    
    <span class="co">// 将当前任务加入等待队列</span>
    waiter.task = current;
    waiter.up = false;
    list_add_tail(&amp;waiter.list, &amp;sem-&gt;wait_list);
    
    <span class="cf">for</span> (;;) {
        <span class="cf">if</span> (signal_pending_state(state, current))
            <span class="cf">goto</span> interrupted;
        <span class="cf">if</span> (unlikely(timeout &lt;= <span class="dv">0</span>))
            <span class="cf">goto</span> timed_out;
            
        __set_current_state(state);
        raw_spin_unlock_irq(&amp;sem-&gt;lock);
        timeout = schedule_timeout(timeout);  <span class="co">// 让出CPU</span>
        raw_spin_lock_irq(&amp;sem-&gt;lock);
        
        <span class="cf">if</span> (waiter.up)  <span class="co">// 被up操作唤醒</span>
            <span class="cf">return</span> <span class="dv">0</span>;
    }
    
 timed_out:
    list_del(&amp;waiter.list);
    <span class="cf">return</span> -ETIME;
    
 interrupted:
    list_del(&amp;waiter.list);
    <span class="cf">return</span> -EINTR;
}

<span class="dt">void</span> up(<span class="kw">struct</span> semaphore *sem)
{
    <span class="dt">unsigned</span> <span class="dt">long</span> flags;
    raw_spin_lock_irqsave(&amp;sem-&gt;lock, flags);
    
    <span class="cf">if</span> (likely(list_empty(&amp;sem-&gt;wait_list))) {
        sem-&gt;count++;  <span class="co">// 无等待者，直接增加计数</span>
    } <span class="cf">else</span> {
        __up(sem);     <span class="co">// 唤醒等待者</span>
    }
    
    raw_spin_unlock_irqrestore(&amp;sem-&gt;lock, flags);
}

<span class="dt">static</span> noinline <span class="dt">void</span> __sched __up(<span class="kw">struct</span> semaphore *sem)
{
    <span class="co">// 获取等待队列中的第一个任务</span>
    <span class="kw">struct</span> semaphore_waiter *waiter = list_first_entry(
        &amp;sem-&gt;wait_list, <span class="kw">struct</span> semaphore_waiter, list);
        
    list_del(&amp;waiter-&gt;list);      <span class="co">// 从队列移除</span>
    waiter-&gt;up = true;            <span class="co">// 标记为已唤醒</span>
    wake_up_process(waiter-&gt;task); <span class="co">// 唤醒任务</span>
}</code></pre></div>
<p>按照我自己学习和看书的理解：</p>
<p>down操作，就是如果没有了信号量，则需要让需要执行的任务让出CPU，就是将需要等待执行的任务链表放在信号量存储的等待队列里面，然后呢up操作的时候检查信号量的等待队列是否任务链表，有的话，将任务链表唤醒</p>
<ul>
<li><strong>内核原子变量的说明和使用</strong></li>
</ul>
<p>如果一些资源非常复杂，不可以让任务进行休眠，可以用原子变量实现，<code>atomic_t</code> 原子操作不会被cpu、打断，也不会被调度程序打断，常用的有<code>atomic_dec_and_test</code>(v--)和<code>atomic_inc</code>(v++)。</p>
<ul>
<li><strong>atomic的内核源码实现</strong></li>
</ul>
<p>因为原子变量用到了汇编，因此先分析一下arm内核源码</p>
<p>```c static inline void atomic_add(int i, atomic_t *v) { unsigned long tmp; // 用于存储strex指令的结果（成功0/失败1） int result; // 存储从内存加载的当前值</p>
<pre><code>// 预取指令：提示CPU即将写入该内存地址，优化缓存性能
prefetchw(&amp;v-&gt;counter);

// 内联汇编开始
__asm__ __volatile__(
    &quot;@ atomic_add\n&quot;     // 汇编注释，表明这是原子加操作
    
    // 循环标签：如果存储失败将跳转回这里重试
    &quot;1: ldrex   %0, [%3]\n&quot;   // 独占加载：%0 = result = *(%3 = &amp;v-&gt;counter)
    &quot;   add %0, %0, %4\n&quot;      // 执行加法：result = result + %4(i)
    &quot;   strex   %1, %0, [%3]\n&quot; // 独占存储：尝试将result写回内存，%1(tmp)=存储结果(0成功/1失败)
    &quot;   teq %1, #0\n&quot;          // 测试tmp是否等于0
    &quot;   bne 1b&quot;                // 如果存储失败(tmp != 0)，跳回标签1重试
    
    // 输出操作数
    : &quot;=&amp;r&quot; (result),   // %0：绑定到result变量（寄存器约束，&amp;表示早期修改）
      &quot;=&amp;r&quot; (tmp),      // %1：绑定到tmp变量
      &quot;+Qo&quot; (v-&gt;counter) // %2：内存操作数，+表示读写，Q表示内存地址约束
    
    // 输入操作数
    : &quot;r&quot; (&amp;v-&gt;counter), // %3：输入参数，v-&gt;counter的地址（寄存器约束）
      &quot;Ir&quot; (i)           // %4：输入参数，立即数i（Ir表示立即数或寄存器）
    
    // 破坏描述部分
    : &quot;cc&quot;              // 表示条件码寄存器被修改
);</code></pre>
<p>} ```</p>
<p>这里总结一下信号量的特点:</p>
<p>1.用于进程和进程之间的同步</p>
<p>2.允许有多个进程进入临界区代码执行</p>
<p>3.进程获取不到信号量锁会陷入休眠，并让出cpu</p>
<p>4.被信号量锁保护的临界区代码允许睡眠</p>
<p>5.本质是基于进程调度器，UP(单核)和SMP(多核)下的实现无差异。</p>
<p>6.不支持进程和中断之间的同步</p>
<ul>
<li><p><strong>spinlock的说明和使用方法</strong></p>
<p>自旋锁是一种死等机制，spinlock自旋锁一次只能有一个程序进入临界区，其他执行单元都是在死等，就算说spilock会一直占用cpu，所以spinlock执行时间会很短，因为不睡眠，自旋锁可以在中断上下文中进行。</p>
<p>spinlock调用自旋锁，spinunlock释放自旋锁。使用自旋锁执行的函数不能太长。</p>
<p>自旋锁常用于 进程与进程、和本地软中断、和本地硬中断的同步。由于自旋锁是一种特殊的机制，自选锁有一个spin_trylock去尝试获取锁，获取不到就不死等。</p></li>
<li><p><strong>spinlock内核源码（UP版）</strong></p></li>
</ul>
<p>同样是以arm为例子</p>
<div class="sourceCode"><pre class="sourceCode c"><code class="sourceCode c"><span class="kw">typedef</span> <span class="kw">struct</span> {
<span class="pp">#ifdef __AARCH64EB__  </span><span class="co">// 大端模式</span>
    u16 next;
    u16 owner;
<span class="pp">#else                 </span><span class="co">// 小端模式</span>
    u16 owner;
    u16 next;
<span class="pp">#endif</span>
} __aligned(<span class="dv">4</span>) arch_spinlock_t;</code></pre></div>
<ul>
<li><strong>spinlock内核源码(SMP版本)</strong></li>
</ul>
<h2 id="char设备驱动到platform驱动的华丽转身">char设备驱动到platform驱动的华丽转身</h2>
<p>Platfrom相当于一个虚拟的总线驱动，就算不在硬件的总线，也可以通过platform进行管理。</p>
<p>使用Platform驱动在Linux设备驱动开发中具有两大核心优势：</p>
<ol style="list-style-type: decimal">
<li><p>硬件与驱动的解耦，驱动注册独立于硬件，通过platform_driver_register()注册纯软件逻辑。设备描述独立存储：硬件资源（地址/中断/时钟）在设备树(DTS)或ACPI表中定义。</p></li>
<li><p>跨平台移植便捷性，相同驱动适配不同硬件：仅需修改设备树，无需重写驱动。</p></li>
</ol>
<p>那么相对于之前简易的字符设备驱动有什么区别呢？</p>
<p>就是将驱动函数的名字需要改变</p>
<div class="sourceCode"><pre class="sourceCode c"><code class="sourceCode c">hello_init() -&gt; hellodev_probe()
hello_exit() -&gt; hellodov_remove() </code></pre></div>
<p>然后多了 platform平台驱动driver结构体 和 平台设备device结构体。</p>
<div class="sourceCode"><pre class="sourceCode c"><code class="sourceCode c">
<span class="dt">static</span> <span class="kw">struct</span> platform_driver hello_driver = {
    .probe = hellodev_probe,
    .remove =hellodov_remove,
        .driver = {
        .name = <span class="st">&quot;hello-dev&quot;</span>,
        .of_match_table = my_of_match, <span class="co">// 设备树支持</span>
    },
};

<span class="kw">struct</span> platform_device hello_device = {
    .name = <span class="st">&quot;hello-device&quot;</span>,
    .id = <span class="dv">-1</span>,
    .num_resources = ARRAY_SIZE(hellodev_resources),
    .resource = hellodev_resources,
};</code></pre></div>
<p>不过现在的device都放在了dts下面了</p>
<p>然后就是编写字符设备初始化</p>
<div class="sourceCode"><pre class="sourceCode c"><code class="sourceCode c">charDevInit
      <span class="co">// 注册平台驱动</span>
    ret = platform_driver_register(&amp;hello_driver);
    <span class="co">//然后匹配驱动的probe函数,执行驱动中的hellodev_probe</span>

charDevExit
   <span class="co">// 注销平台驱动</span>
    platform_driver_unregister(&amp;my_platform_driver);</code></pre></div>
<ul>
<li><p><strong>源码角度分析platform设备和驱动的匹配原则</strong></p>
<p>platform 是一条虚拟总线，可以将一些设备放置在该虚拟设备总线上，设备为 platform_device，要操作这些设备需要使用匹配的驱动，驱动为 platform_driver，当执行 insmod 时，驱动会在总线上查找与其对应的设备，查找成功后执行 probe 动作进行设备初始化，当驱动卸载后执行 remove 函数，进行退出动作。</p></li>
</ul>
<div class="sourceCode"><pre class="sourceCode c"><code class="sourceCode c"><span class="kw">struct</span> bus_type platform_bus_type = {
    .name       = <span class="st">&quot;platform&quot;</span>,
    .dev_groups = platform_dev_groups,
    .match      = platform_match, <span class="co">// 核心匹配函数</span>
    .uevent     = platform_uevent,
    .pm     = &amp;platform_dev_pm_ops,
};
EXPORT_SYMBOL_GPL(platform_bus_type);

<span class="dt">static</span> <span class="dt">int</span> platform_match(<span class="kw">struct</span> device *dev, <span class="kw">struct</span> device_driver *drv)
{
    <span class="kw">struct</span> platform_device *pdev = to_platform_device(dev);
    <span class="kw">struct</span> platform_driver *pdrv = to_platform_driver(drv);
    
    <span class="co">/* 1. 检查 driver_override 强制匹配 */</span>
    <span class="cf">if</span> (pdev-&gt;driver_override)
        <span class="cf">return</span> !strcmp(pdev-&gt;driver_override, drv-&gt;name);
    
    <span class="co">/* 2. 设备树匹配（现代首选） */</span>
    <span class="cf">if</span> (of_driver_match_device(dev, drv))
        <span class="cf">return</span> <span class="dv">1</span>;
    
    <span class="co">/* 3. ACPI 匹配 */</span>
    <span class="cf">if</span> (acpi_driver_match_device(dev, drv))
        <span class="cf">return</span> <span class="dv">1</span>;
    
    <span class="co">/* 4. ID 表匹配 */</span>
    <span class="cf">if</span> (pdrv-&gt;id_table)
        <span class="cf">return</span> platform_match_id(pdrv-&gt;id_table, pdev) != NULL;
    
    <span class="co">/* 5. 名称直接匹配 */</span>
    <span class="cf">return</span> (strcmp(pdev-&gt;name, drv-&gt;name) == <span class="dv">0</span>);
}

<span class="co">//设备树匹配函数</span>
<span class="dt">static</span> <span class="kw">inline</span> <span class="dt">int</span> of_driver_match_device(<span class="kw">struct</span> device *dev,
                    <span class="dt">const</span> <span class="kw">struct</span> device_driver *drv)
{
    <span class="cf">return</span> of_match_device(drv-&gt;of_match_table, dev) != NULL;
}</code></pre></div>
<ul>
<li>1 强制匹配</li>
</ul>
<div class="sourceCode"><pre class="sourceCode c"><code class="sourceCode c"><span class="co">// 用户强制指定驱动</span>
echo <span class="st">&quot;my_driver&quot;</span> &gt; /sys/bus/platform/devices/my_device/driver_override</code></pre></div>
<ul>
<li>2 设备树匹配(查看compatible的属性)</li>
</ul>
<p>```c // 设备树节点 my_device: my-device@12340000 { compatible = &quot;vendor,device-1&quot;, &quot;vendor,device-generic&quot;; reg = &lt;0x12340000 0x1000&gt;; };</p>
<p>// 驱动中的匹配表 static const struct of_device_id my_of_match[] = { { .compatible = &quot;vendor,device-1&quot; }, { .compatible = &quot;vendor,device-2&quot; }, {} // 结束标记 }; MODULE_DEVICE_TABLE(of, my_of_match); ```</p>
<ul>
<li>3 ACPI 匹配</li>
</ul>
<p>用于 x86 等 ACPI 系统</p>
<p>```c static int acpi_driver_match_device(struct device <em>dev, struct device_driver </em>drv) { const struct acpi_device_id *acpi_ids = drv-&gt;acpi_match_table;</p>
<pre><code>if (!acpi_ids)
    return 0;

return acpi_match_device_ids(to_acpi_device(dev), acpi_ids) == 0;</code></pre>
<p>} ```</p>
<ul>
<li>4 ID 表匹配</li>
</ul>
<p>```c // 驱动定义 ID 表 static const struct platform_device_id my_id_table[] = { { &quot;device_v1&quot;, 0 }, // 匹配设备名为 &quot;device_v1&quot; { &quot;device_v2&quot;, 0 }, {} // 结束标记 };</p>
<p>// 平台驱动结构 static struct platform_driver my_driver = { .driver = { .name = &quot;generic_device&quot;, }, .id_table = my_id_table, // ID 表 .probe = my_probe, .remove = my_remove, }; ```</p>
<ul>
<li>5 名称直接匹配</li>
</ul>
<p>```c // 设备 struct platform_device my_device = { .name = &quot;specific_device&quot;, // 必须与驱动名匹配 };</p>
<p>// 驱动 static struct platform_driver my_driver = { .driver = { .name = &quot;specific_device&quot;, // 与设备名相同 }, .probe = my_probe, }; ```</p>
<h2 id="页框和伙伴算法以及slab机制">页框和伙伴算法以及slab机制</h2>
<ul>
<li><p><strong>页框</strong></p>
<p>内存中的内存块用struct page 描述，一般大小位4K</p>
<p>内核对于有些场景，需要申请<strong>大小不定</strong>的连续页框。所以需要用伙伴算法进行分配。</p></li>
<li><p><strong>伙伴算法</strong></p>
<p>怎么分配的呢：首先将linux内核中的空闲页框分组为11个链表，每个链表中的页框块是固定的 第i个链表中每个页框块都包含2的i次方个连续页。</p>
<p>就比如这个链表数组，第一个链表中包含了2^0 = 1个连续的页框，第4个链表包含了2^4个连续 的页框。</p>
<p>最大可申请的连续物理块是2^10 = 1024 个的连续页框，一个页框是4k，那么可以申请4M大小。</p>
<p>内存分配的逻辑无非就是查看链表里面是否有大小满足的，不满足则继续往下找，如果被占用也继续往 下找，找到一个可以分割一部分页框给这个任务的。 释放内存的逻辑则是判断这个连续的内存他的一些 页框有没有被释放，被释放了则合并。</p></li>
</ul>
</body>
</html>
